using System;
using System.IO;
using System.Management;
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Xml.Serialization;

namespace Microsoft.Sdc.Tasks.Configuration.InstalledProducts
{
	/// <summary>
	/// "Your WMI script uses the Win32_Product class, which the WMI Windows
	///Installer Provider services. In XP and earlier, the WMI Windows Installer
	///Provider is installed by default. Unfortunately, that's no longer the case
	///beginning with Windows 2003. Instead, the WMI Windows Installer Provider is
	///an optional component, as Figure 1, page 10, shows. To install this
	///provider, open the Control Panel Add/Remove Programs applet and click
	///Add/Remove Windows Components. Select the Management and Monitoring Tools
	///check box, then click Details. Select the WMI Windows Installer Provider
	///check box, and click OK."
	/// </summary>
	internal class InstalledProductManager
	{			
		/// <summary>
		/// Serialize the product list into an Xml string
		/// </summary>
		/// <param name="products"></param>
		/// <returns></returns>
		public string ConvertToXml(ProductList products)
		{
			XmlSerializer serializer = new XmlSerializer(typeof(ProductList));
			StringWriter sw = new StringWriter();
			serializer.Serialize(sw,products);
			return sw.ToString();
		}

		/// <summary>
		/// Deserialize from xml to ProductList
		/// </summary>
		/// <param name="xml">Xml produced by the ConvertToXml method</param>
		/// <returns>List of Product objects</returns>
		public ProductList ConvertFromXml(string xml)
		{
			XmlSerializer serializer = new XmlSerializer(typeof(ProductList));			
			StringReader sr = new StringReader(xml);
			return (ProductList)serializer.Deserialize(sr);
		}

		/// <summary>
		/// Check to see if a product is installed
		/// </summary>
		/// <param name="criteria">A list of Criterion objects (e.g. StringCriterion)</param>
		/// <param name="products">The installed products to on the machine to match against</param>
		/// <returns>True if criteria match at least one of the products</returns>
		public bool IsInstalled(Criteria criteria, ProductList products)
		{			
			return (GetMatchingInstalledProduct(criteria,products) != null);
		}
		
		public Product GetMatchingInstalledProduct(Criteria criteria, ProductList products)
		{
			foreach (Product product in products)
			{
				try
				{
					if (criteria.IsMatch(product))
					{
						return product;
					}
				}
				catch (System.FormatException)
				{
					// Some products may have incompatible versions that cannot be compared
					// These will not match and will be ignored
				}
			}
			return null;
		}

		/// <summary>
		/// Get installed products on the local machine
		/// </summary>
		/// <returns></returns>
		public ProductList GetInstalledProducts()
		{
			return GetInstalledProducts(".",null,null);
		}

		/// <summary>
		/// Get a list of installed products on a remote machine using impersonation
		/// </summary>		
		/// <param name="machine">Remote machine or "." for local machine</param>
		public ProductList GetInstalledProducts(string machine)
		{
			return GetInstalledProducts(machine,null,null);
		}

		/// <summary>
		/// Get a list of installed products
		/// </summary>		
		/// <remarks>
		/// If a username is not specified but the machine is not local (i.e. not "."),
		/// Impersonation will be used to authenticate with the remote machine.
		/// </remarks>
		/// <param name="machine">Remote machine or "." for local machine</param>
		/// <param name="username">Optional username (or null)</param>
		/// <param name="password">Optional password (or null)</param>
		/// <returns>List of Product objects</returns>
		public ProductList GetInstalledProducts(string machine, string username, string password)
		{
			ConnectionOptions opts=null;

			ProductList apps = new ProductList();
			if (username != null)
			{
				opts = new ConnectionOptions();					
				opts.Username =username;
				opts.Password =password;				
			}
			else if (machine != ".")
			{
				opts = new ConnectionOptions();					
				opts.Impersonation = ImpersonationLevel.Impersonate;
				opts.Authentication = AuthenticationLevel.Default;				
			}

			ManagementScope scope = new ManagementScope(String.Format(@"\\{0}\root\CIMv2",machine),opts);
			scope.Connect();

			ManagementPath path = new ManagementPath();
			path.ClassName="Win32_Product";
			ManagementClass win32Product = new ManagementClass(scope,path,null);

			try
			{
				ManagementObjectCollection products = win32Product.GetInstances();

				foreach (ManagementObject w32Product in products)
				{
					Product product = new Product();
					product.Name = (string)w32Product["Name"];
					product.Caption = (string)w32Product["Caption"];
					product.Description = (string)w32Product["Description"];
					product.IdentifyingNumber = (string)w32Product["IdentifyingNumber"];
					product.InstallDate = (string)w32Product["InstallDate"];
					
					// TODO: parse this date format
					//product.InstallDate2 = DateTime.Parse((string)w32Product["InstallDate2"]);
					product.InstallLocation = (string)w32Product["InstallLocation"];
					product.InstallState = (InstallState)Enum.Parse(typeof(InstallState),Convert.ToString(w32Product["InstallState"]));
					product.PackageCache = (string)w32Product["PackageCache"];
					product.SKUNumber = (string)w32Product["SKUNumber"];
					product.Vendor =  (string)w32Product["Vendor"];
					product.Version = (string)w32Product["Version"];				

					if (product.InstallState == InstallState.Installed)
					{
						apps.Add(product);
					}
				}
			}
			catch (ManagementException e)
			{
				throw new ApplicationException("Could not get installed products list. If using Windows 2003, this is an optional Windows Component (under Management and Monitoring Tools->WMI Windows Installer Provider)",e);
			}
			return apps;
		}

		/// <summary>
		/// Convenience method to add a criterion for And-ing
		/// </summary>
		/// <param name="criteria"></param>
        /// <param name="criterion"></param>
        /// <returns></returns>
		public void AddForAnd(Criteria criteria, Criterion criterion)
		{	
			bool isFirst = (criteria.Count==0);
			criteria.Push(criterion.Value); // value to check against (RHS)
			criteria.Push(criterion.PropertyToExamine); // property info of product (LHS)
			criteria.Push(criterion.ComparisonChecker); // version comparison checks or string comparison
			criteria.Push(criterion.Operator); // not or none	

			if (! isFirst)
			{
				criteria.Push(BinaryLogicalOperator.And);
			}
		}				

		public InstalledProductManager()
		{
		}
	}
}
